﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;

namespace Pibby.Core.Presenters
{
	public class BackgroundProcessing : DisposableObject
	{
		#region Attributes
		
		BackgroundWorker _worker;
		
		Delegate _onStartupMethod;
		Delegate _onProgressMethod;
		Delegate _onCompletionMethod;

		Action<Exception> _onExceptionMethod; 
		
		object _cookie;

		#endregion Attributes

		#region Factory Methods

		public static BackgroundProcessing Create<TInitialObject, TProgressObject>(Action<BackgroundWorker, TInitialObject> startMethod, Action<TProgressObject> progressMethod, Action completionMethod, Action<Exception> exceptionHandler, TInitialObject startObject)
		{
			return new BackgroundProcessing(startMethod, progressMethod, completionMethod, exceptionHandler, startObject);
		}

		public static BackgroundProcessing Create<TInitialObject>(Action<BackgroundWorker, TInitialObject> startMethod, Action completionMethod, Action<Exception> exceptionHandler, TInitialObject startObject)
		{
			return new BackgroundProcessing(startMethod, null, completionMethod, exceptionHandler, startObject);
		}

		#endregion Factory Methods

		#region Constructors
		
		private BackgroundProcessing()
		{
			this._worker = new BackgroundWorker()
				{
					WorkerSupportsCancellation = true,
					WorkerReportsProgress = true
				};

			this.SubscribeToEvents();
		}

		private BackgroundProcessing(Delegate entryDelegate, Delegate progressDelegate, Delegate callbackDelegate, Action<Exception> exceptionHandler, object parameter)   
			: this()
		{
			this._onStartupMethod = entryDelegate;
			this._onProgressMethod = progressDelegate;
			this._onCompletionMethod = callbackDelegate;
			this._cookie = parameter;
			this._onExceptionMethod = exceptionHandler;
		}

		private void SubscribeToEvents()
		{
			this._worker.DoWork += DoWorkHandler;
			this._worker.RunWorkerCompleted += RunWorkerCompletedHandler;
			this._worker.ProgressChanged += WorkerProgressChangedHandler;
		}

		private void UnsubscribeToEvents()
		{
			this._worker.DoWork -= DoWorkHandler;
			this._worker.RunWorkerCompleted -= RunWorkerCompletedHandler;
			this._worker.ProgressChanged -= WorkerProgressChangedHandler;
		}

		#endregion Constructors

		#region IDisposable

		~BackgroundProcessing()
		{
			this.Dispose(false);
		}

		protected override void ReleaseManagedResources()
		{
			base.ReleaseManagedResources();

			if (this._worker.IsBusy)
			{
				this._worker.CancelAsync();
			}

			this.UnsubscribeToEvents();

			this._worker.Dispose();
			this._worker = null; 
		}


		#endregion IDisposable

		#region Methods
		
		public void Run()
		{
			if (this.Disposed)
				throw new ObjectDisposedException("BackgroundProcessing");

			if (this._cookie != null)
				_worker.RunWorkerAsync(this._cookie);
			else
				_worker.RunWorkerAsync();
		}
		
		public void Cancel()
		{
			this.Dispose();
		}
		
		#endregion

		#region Event Handlers


		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
		void DoWorkHandler(object sender, DoWorkEventArgs e) 
		{ 
			try 
			{ 
				object[] parameters = null; 
				
				if (e.Argument != null) 
				{ 
					parameters = new object[] { this._worker, e.Argument }; 
				} 
				
				e.Result = this._onStartupMethod.Method.Invoke(this._onStartupMethod.Target, parameters); 
			} 
			catch (Exception ex) 
			{ 
				e.Result = ex; 
			} 
			
			BackgroundWorker worker = sender as BackgroundWorker; 
			
			e.Cancel = worker.CancellationPending; 
		}

		void WorkerProgressChangedHandler(object sender, ProgressChangedEventArgs e)
		{
			if (this._onProgressMethod != null && e.UserState != null)
			{
				object[] parameters = new object[] { e.UserState };

				this._onProgressMethod.Method.Invoke(this._onProgressMethod.Target, parameters);
			}
		}

		void RunWorkerCompletedHandler(object sender, RunWorkerCompletedEventArgs e) 
		{
			if (e.Error != null && this._onExceptionMethod != null)
			{
				this._onExceptionMethod(e.Error); 
			}
			else if (!e.Cancelled && e.Error == null)
			{
				Exception exception = e.Result as Exception;

				if (exception != null)
				{
					this._onExceptionMethod(exception);
				}
				else
				{
					if (this._onCompletionMethod != null)
					{
						object[] parameters = null;

						if (e.Result != null)
						{
							parameters = new object[] { e.Result };
						}

						this._onCompletionMethod.Method.Invoke(this._onCompletionMethod.Target, parameters);
					}
				}
			}
			
			this.Dispose(); 
		}

		#endregion Event Handlers
	}
}
